﻿using Newtonsoft.Json;
using System.Collections.ObjectModel;
using System.Reflection;

namespace AutoRevit.Storage.Extensions;

/// <summary>
/// 
/// </summary>
public static class StorageOnElementExtensions
{
    /// <summary>
    /// 扩展数据允许使用的字段类型
    /// </summary>
    private static readonly ReadOnlyCollection<Type> allowFieldTypes = new ReadOnlyCollection<Type>(new List<Type>
        {
            typeof(int),
            typeof(short),
            typeof(byte),
            typeof(double),
            typeof(float),
            typeof(bool),
            typeof(string),
            typeof(Guid),
            typeof(ElementId),
            typeof(XYZ),
            typeof(UV),
        });

    private static FieldInfo[] GetFields(Type type)
    {
        return type.GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Default);
    }

    private static PropertyInfo[] GetProperties(Type type)
    {
        return type.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Default);
    }

    /// <summary>
    /// 查找 泛型 TStorage 实例的 SchemaBuilderInfo 并返回
    /// </summary>
    /// <typeparam name="TStorage"></typeparam>
    /// <param name="info"></param>
    /// <returns></returns>
    private static bool TryGetSchemaBuilderInfoAttribute<TStorage>(out SchemaBuilderInfoAttribute info)
    {
        Type type = typeof(TStorage);

        info = type.GetCustomAttribute<SchemaBuilderInfoAttribute>()!;

        return !(info is null);
    }

    #region 静态私有扩展

    /// <summary>
    /// 设置值
    /// </summary>
    /// <param name="entity"></param>
    /// <param name="valueName"></param>
    /// <param name="valueType"></param>
    /// <param name="value"></param>
    private static void SetEntityValue(Entity entity, string valueName, Type valueType, object? value)
    {
        if (valueType == typeof(int))
        {
            entity.Set(valueName, (int)value!);
        }
        else if (valueType == typeof(short))
        {
            entity.Set(valueName, (short)value!);
        }
        else if (valueType == typeof(byte))
        {
            entity.Set(valueName, (byte)value!);
        }
        else if (valueType == typeof(double))
        {
            entity.Set(valueName, (double)value!);
        }
        else if (valueType == typeof(float))
        {
            entity.Set(valueName, (float)value!);
        }
        else if (valueType == typeof(bool))
        {
            entity.Set(valueName, (bool)value!);
        }
        else if (valueType == typeof(string))
        {
            entity.Set(valueName, (string)value!);
        }
        else if (valueType == typeof(Guid))
        {
            entity.Set(valueName, (Guid)value!);
        }
        else if (valueType == typeof(ElementId))
        {
            entity.Set(valueName, value as ElementId);
        }
        else if (valueType == typeof(XYZ))
        {
            entity.Set(valueName, value as XYZ);
        }
        else if (valueType == typeof(UV))
        {
            entity.Set(valueName, value as UV);
        }
        else
        {
            entity.Set(valueName, JsonConvert.SerializeObject(value));
        }
    }

    /// <summary>
    /// 获取值
    /// </summary>
    /// <param name="entity"></param>
    /// <param name="field"></param>
    /// <param name="valueType"></param>
    /// <returns></returns>
    private static object? GetEntityValue(Entity entity, Field field, Type valueType)
    {
        if (valueType == typeof(int))
        {
            return entity.Get<int>(field);
        }
        else if (valueType == typeof(short))
        {
            return entity.Get<short>(field);
        }
        else if (valueType == typeof(byte))
        {
            return entity.Get<byte>(field);
        }
        else if (valueType == typeof(double))
        {
            return entity.Get<double>(field);
        }
        else if (valueType == typeof(float))
        {
            return entity.Get<float>(field);
        }
        else if (valueType == typeof(bool))
        {
            return entity.Get<bool>(field);
        }
        else if (valueType == typeof(string))
        {
            return entity.Get<string>(field);
        }
        else if (valueType == typeof(Guid))
        {
            return entity.Get<Guid>(field);
        }
        else if (valueType == typeof(ElementId))
        {
            return entity.Get<ElementId>(field);
        }
        else if (valueType == typeof(XYZ))
        {
            return entity.Get<XYZ>(field);
        }
        else if (valueType == typeof(UV))
        {
            return entity.Get<UV>(field);
        }
        else
        {
            return JsonConvert.DeserializeObject(entity.Get<string>(field), valueType);
        }
        //return default(object);
    }

    /// <summary>
    /// 创建 Schema
    /// </summary>
    /// <typeparam name="TStorage"></typeparam>
    /// <param name="info"></param>
    /// <param name="guid"></param>
    /// <returns></returns>
    private static Schema CreateSchemaForAttribute<TStorage>(SchemaBuilderInfoAttribute info, Guid? guid = null)
    {
        if (!guid.HasValue)
        {
            guid = typeof(TStorage).GUID;
        }

        SchemaBuilder bulder = new SchemaBuilder(guid.Value)
                .SetReadAccessLevel(info.ReadAccessLevel)
                .SetWriteAccessLevel(info.WriteAccessLevel);

        Type type = typeof(TStorage);

        if (string.IsNullOrWhiteSpace(info.SchemaName))
        {
            string name = type.FullName!.Replace(".", "_").Replace('+', '_');
            bulder.SetSchemaName(name);
        }
        else
        {
            bulder.SetSchemaName(info.SchemaName);
        }
        if (info.ApplicationGUID.HasValue)
        {
            bulder.SetApplicationGUID(info.ApplicationGUID.Value);
        }

        if (!string.IsNullOrWhiteSpace(info.VendorId))
        {
            bulder.SetVendorId(info.VendorId);
        }

        if (!string.IsNullOrWhiteSpace(info.Documentation))
        {
            bulder.SetDocumentation(info.Documentation);
        }

        foreach (var field in GetFields(type))
        {
            if (allowFieldTypes.Any(t => t == field.FieldType))
            {
                bulder.AddSimpleField(field.Name, field.FieldType).Dispose();
            }
            else
            {
                bulder.AddSimpleField(field.Name, typeof(string)).Dispose();
            }
        }

        foreach (var property in GetProperties(type))
        {
            if (allowFieldTypes.Any(t => t == property.PropertyType))
            {
                bulder.AddSimpleField(property.Name, property.PropertyType).Dispose();
            }
            else
            {
                bulder.AddSimpleField(property.Name, typeof(string)).Dispose();
            }
        }

        var schema = bulder.Finish();

        bulder.Dispose();

        return schema;
    }

    /// <summary>
    /// 创建 Schema
    /// </summary>
    /// <param name="info"></param>
    /// <param name="guid"></param>
    /// <param name="fieldMap"></param>
    /// <returns></returns>
    private static Schema CreateSchemaForAttribute(SchemaBuilderInfoAttribute info, Guid guid, Dictionary<string, Type> fieldMap)
    {
        SchemaBuilder bulder = new SchemaBuilder(guid)
                .SetReadAccessLevel(info.ReadAccessLevel)
                .SetWriteAccessLevel(info.WriteAccessLevel);

        if (string.IsNullOrWhiteSpace(info.SchemaName))
        {
            throw new ArgumentNullException("SchemaBuilderInfo 的 SchemaName 属性未设置");
        }
        else
        {
            bulder.SetSchemaName(info.SchemaName);
        }
        if (info.ApplicationGUID.HasValue)
        {
            bulder.SetApplicationGUID(info.ApplicationGUID.Value);
        }

        if (!string.IsNullOrWhiteSpace(info.VendorId))
        {
            bulder.SetVendorId(info.VendorId);
        }

        if (!string.IsNullOrWhiteSpace(info.Documentation))
        {
            bulder.SetDocumentation(info.Documentation);
        }

        foreach (var fieldInfo in fieldMap)
        {
            if (allowFieldTypes.Any(t => t == fieldInfo.Value))
            {
                bulder.AddSimpleField(fieldInfo.Key, fieldInfo.Value).Dispose();
            }
            else
            {
                bulder.AddSimpleField(fieldInfo.Key, typeof(string)).Dispose();
            }
        }

        var schema = bulder.Finish();

        bulder.Dispose();

        return schema;
    }

    /// <summary>
    /// 设置扩展数据实体数据
    /// </summary>
    /// <typeparam name="TStorage"></typeparam>
    /// <param name="entity"></param>
    /// <param name="storage"></param>
    private static Entity SetEntity<TStorage>(Entity entity, TStorage storage)
    {
        Type type = typeof(TStorage);

        foreach (var field in GetFields(type))
        {
            SetEntityValue(entity, field.Name, field.FieldType, field.GetValue(storage));
        }

        foreach (var property in GetProperties(type))
        {
            SetEntityValue(entity, property.Name, property.PropertyType, property.GetValue(storage));
        }
        return entity;
    }

    /// <summary>
    /// 设置扩展数据实体数据
    /// </summary>
    /// <param name="entity"></param>
    /// <param name="values"></param>
    /// <returns></returns>
    private static Entity SetEntity(Entity entity, Dictionary<string, object> values)
    {
        foreach (var item in values)
        {
            SetEntityValue(entity, item.Key, item.Value.GetType(), item.Value);
        }

        return entity;
    }

    /// <summary>
    /// 获取构建扩展数据
    /// </summary>
    /// <param name="element"></param>
    /// <param name="schemaGuid"></param>
    /// <returns></returns>
    private static Entity? GetEntity(Element element, Guid schemaGuid)
    {
        var schema = Schema.Lookup(schemaGuid);
        if (schema is null)
        {
            return null;
        }
        return element.GetEntity(schema);
    }

    /// <summary>
    /// 检查数据实体是否有效
    /// </summary>
    /// <param name="entity"></param>
    /// <returns></returns>
    private static bool CanValidOnEntity(Entity? entity) => entity != null && entity.SchemaGUID.ToString() != "00000000-0000-0000-0000-000000000000" && entity.Schema != null;

    /// <summary>
    /// 获取构建扩展数据并返回值
    /// </summary>
    /// <param name="element"></param>
    /// <param name="schemaGuid"></param>
    /// <param name="entity"></param>
    /// <returns></returns>
    private static bool TryEntity(Element element, Guid schemaGuid, out Entity? entity)
    {
        entity = GetEntity(element, schemaGuid);
        return CanValidOnEntity(entity);
    }

    /// <summary>
    /// 获取构建扩展数据并返回值
    /// </summary>
    /// <param name="element"></param>
    /// <param name="schemaName"></param>
    /// <param name="entity"></param>
    /// <returns></returns>
    private static bool TryEntity(Element element, string schemaName, out Entity entity)
    {
        entity = null!;

        var schema = element.GetEntitySchemaGuids().Select(t => Schema.Lookup(t)).FirstOrDefault(t => t.SchemaName == schemaName);

        if (schema is null)
        {
            return false;
        }

        entity = element.GetEntity(schema);

        return CanValidOnEntity(entity);
    }

    #endregion 静态私有扩展

    #region GET

    /// <summary>
    /// 获取值
    /// </summary>
    /// <typeparam name="FieldType">字段类型</typeparam>
    /// <param name="element">获取的对象构件</param>
    /// <param name="schemaName">扩展数据名称</param>
    /// <param name="fieldName">字段名称</param>
    /// <returns></returns>
    public static FieldType GetEntityValue<FieldType>(this Element element, string schemaName, string fieldName)
    {
        if (TryEntity(element, schemaName, out Entity entity))
        {
            Type type = typeof(FieldType);
            if (allowFieldTypes.Any(t => t == type))
            {
                return entity.Get<FieldType>(fieldName);
            }
            else
            {
                return (FieldType)JsonConvert.DeserializeObject(entity.Get<string>(fieldName), type)!;
            }
        }
        else
        {
            throw new Exception("该构建未找到相应的扩展数据");
        }
    }

    /// <summary>
    ///获取值
    /// </summary>
    /// <typeparam name="FieldType">字段类型</typeparam>
    /// <param name="element">获取的对象构件</param>
    /// <param name="schemaName">扩展数据名称</param>
    /// <param name="fieldName">字段名称</param>
    /// <param name="failToValue">如果不存在扩展数据值则放回该参数值</param>
    /// <returns></returns>
    public static FieldType GetEntityValue<FieldType>(this Element element, string schemaName, string fieldName, FieldType failToValue)
    {
        if (TryEntity(element, schemaName, out Entity entity))
        {
            Type type = typeof(FieldType);
            if (allowFieldTypes.Any(t => t == type))
            {
                return entity.Get<FieldType>(fieldName);
            }
            else
            {
                return (FieldType)JsonConvert.DeserializeObject(entity.Get<string>(fieldName), type)!;
            }
        }
        else
        {
            return failToValue;
        }
    }

    /// <summary>
    /// 获取值
    /// </summary>
    /// <typeparam name="FieldType">字段类型</typeparam>
    /// <param name="element">获取的对象构件</param>
    /// <param name="valueName">值名称</param>
    /// <returns></returns>
    public static FieldType GetEntityValue<FieldType>(this Element element, string valueName)
    {
        return element.GetEntityValue<FieldType>(valueName, "Value");
    }

    /// <summary>
    /// 获取值
    /// </summary>
    /// <typeparam name="FieldType">字段类型</typeparam>
    /// <param name="element">获取的对象构件</param>
    /// <param name="valueName">值名称</param>
    /// <param name="failToValue">如果不存在扩展数据值则放回该参数值</param>
    /// <returns></returns>
    public static FieldType GetEntityValue<FieldType>(this Element element, string valueName, FieldType failToValue)
    {
        return element.GetEntityValue(valueName, "Value", failToValue);
    }

    /// <summary>
    /// 尝试获取值
    /// </summary>
    /// <typeparam name="FieldType">字段类型</typeparam>
    /// <param name="element">获取的对象构件</param>
    /// <param name="schemaName">扩展数据名称</param>
    /// <param name="fieldName">字段名称</param>
    /// <param name="value">返回值</param>
    /// <returns></returns>
    public static bool TryGetEntityValue<FieldType>(this Element element, string schemaName, string fieldName, out FieldType value)
    {
        value = default!;

        try
        {
            value = element.GetEntityValue<FieldType>(schemaName, fieldName);

            return true;
        }
        catch
        {
            return false;
        }
    }

    /// <summary>
    /// 尝试获取值
    /// </summary>
    /// <typeparam name="FieldType">字段类型</typeparam>
    /// <param name="element">获取的对象构件</param>
    /// <param name="valueName">值名称</param>
    /// <param name="value">返回值</param>
    /// <returns></returns>
    public static bool TryGetEntityValue<FieldType>(this Element element, string valueName, out FieldType value)
    {
        return element.TryGetEntityValue(valueName, "Value", out value);
    }

    /// <summary>
    /// 获取实体
    /// </summary>
    /// <typeparam name="TStorage"></typeparam>
    /// <param name="element">获取的对象构件</param>
    /// <param name="schemaName">扩展数据名称</param>
    /// <returns></returns>
    /// <exception cref="System.ArgumentNullException"></exception>
    public static TStorage? GetEntityStorage<TStorage>(this Element element, string schemaName = "") where TStorage : class
    {
        if (string.IsNullOrWhiteSpace(schemaName))
        {
            SchemaBuilderInfoAttribute info;

            if (TryGetSchemaBuilderInfoAttribute<TStorage>(out info) && !string.IsNullOrWhiteSpace(info.SchemaName))
            {
                //throw new ArgumentNullException($"泛型 {typeof(TStorage).FullName} 未标记特性 {typeof(SchemaBuilderInfo).FullName}");
                schemaName = info.SchemaName!;
            }
            else
            {
                schemaName = typeof(TStorage).Name;
            }
        }

        var schema = element.GetEntitySchemaGuids().Select(t => Schema.Lookup(t)).FirstOrDefault(t => t.SchemaName == schemaName);

        if (schema is null)
        {
            return null;
        }

        Entity entity = element.GetEntity(schema);

        if (!CanValidOnEntity(entity))
        {
            throw new Exception("获取构建的扩展数据模型不存在");
        }

        Type storageType = typeof(TStorage);

        TStorage storage = Activator.CreateInstance<TStorage>();

        foreach (var f in schema.ListFields())
        {
            var fieldInfo = GetFields(storageType).FirstOrDefault(t => t.Name == f.FieldName);

            if (fieldInfo != null)
            {
                fieldInfo.SetValue(storage, GetEntityValue(entity, f, fieldInfo.FieldType));
                continue;
            }

            var propertyInfo = GetProperties(storageType).FirstOrDefault(t => t.Name == f.FieldName);

            if (propertyInfo != null)
            {
                propertyInfo.SetValue(storage, GetEntityValue(entity, f, propertyInfo.PropertyType));
            }
        }

        return storage;
    }

    /// <summary>
    /// 获取实体
    /// </summary>
    /// <typeparam name="TStorage"></typeparam>
    /// <param name="element">获取的对象构件</param>
    /// <param name="schemaName">扩展数据名称</param>
    /// <param name="storage">扩展数据对象</param>
    /// <returns></returns>
    public static bool TryGetEntityStorage<TStorage>(this Element element, string schemaName, out TStorage storage) where TStorage : class
    {
        storage = element.GetEntityStorage<TStorage>(schemaName)!;

        if (storage is null)
        {
            return false;
        }

        return true;
    }

    #endregion GET

    #region SET

    /// <summary>
    /// 设置扩展数据信息
    /// </summary>
    /// <typeparam name="TStorage"></typeparam>
    /// <param name="element"></param>
    /// <param name="storage"></param>
    /// <param name="info"></param>
    /// <exception cref="System.ArgumentNullException"></exception>
    public static void SetEntityStorage<TStorage>(this Element element,
                                                  TStorage storage,
                                                  SchemaBuilderInfoAttribute? info = null) where TStorage : class
    {
        if (info is null)
        {
            if (TryGetSchemaBuilderInfoAttribute<TStorage>(out info) && !string.IsNullOrWhiteSpace(info.SchemaName))
            {
                //throw new ArgumentNullException($"泛型 {typeof(TStorage).FullName} 未标记特性 {typeof(SchemaBuilderInfo).FullName}");
                info.SchemaName = typeof(TStorage).Name;
            }
            else
            {
                info = new SchemaBuilderInfoAttribute() { SchemaName = typeof(TStorage).Name };
            }
        }

        Schema schema = CreateSchemaForAttribute<TStorage>(info, Guid.NewGuid());

        var currentSchema = element.GetEntitySchemaGuids().Select(t => Schema.Lookup(t)).FirstOrDefault(t => t.SchemaName == schema.SchemaName);

        if (currentSchema != null)
        {
            element.DeleteEntity(currentSchema);
        }

        Entity entity = new Entity(schema);

        element.SetEntity(SetEntity(entity, storage));
    }

    /// <summary>
    /// 设置扩展数据值
    /// </summary>
    /// <typeparam name="FieldType"></typeparam>
    /// <param name="element"></param>
    /// <param name="valueName"></param>
    /// <param name="value"></param>
    public static void SetEntityValue<FieldType>(Element element, string valueName, FieldType value)
    {
        SchemaBuilderInfoAttribute info = new SchemaBuilderInfoAttribute() { SchemaName = valueName };

        Schema schema = CreateSchemaForAttribute(info, Guid.NewGuid(), new Dictionary<string, Type> { { "Value", typeof(FieldType) } });

        var currentSchema = element.GetEntitySchemaGuids().Select(t => Schema.Lookup(t)).FirstOrDefault(t => t.SchemaName == schema.SchemaName);

        if (currentSchema != null)
        {
            element.DeleteEntity(currentSchema);
        }

        Entity entity = new Entity(schema);

        element.SetEntity(SetEntity(entity, new Dictionary<string, object> { { "Value", value! } }));
    }

    #endregion SET

    #region DELETE

    /// <summary>
    /// 删除扩展数据
    /// </summary>
    /// <param name="element"></param>
    /// <param name="schemaName"></param>
    public static void DelEntityStorage(this Element element, string schemaName)
    {
        var currentSchema = element.GetEntitySchemaGuids().Select(t => Schema.Lookup(t)).FirstOrDefault(t => t.SchemaName == schemaName);

        if (currentSchema != null)
        {
            element.DeleteEntity(currentSchema);
        }
    }

    /// <summary>
    /// 删除所有扩展数据
    /// </summary>
    /// <param name="element"></param>
    public static void DelAllEntityStorage(this Element element)
    {
        foreach (Schema schema in element.GetEntitySchemaGuids().Select(t => Schema.Lookup(t)))
        {
            element.DeleteEntity(schema);
        }
    }

    #endregion DELETE
}
